package deatax;

import deatax.display.miniboard.RevolutionListener;
import deatax.network.Client;
import deatax.network.Messages;
import deatax.unit.UnitStack;
import java.awt.Color;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
/**
 *
 * @author pson
 */
public class Player implements RevolutionListener {

    /**
     * Player name
     */
    private GameData data;
    private String name;
    private Color color;
//    private int mbPosition = 0;
    /**
     * Amount of each resource the player has
     */
    private ArrayList<ResourceAmount> resources;
    private ArrayList<Tile> tiles;
    private ArrayList<Tile> units;
    private ArrayList<Tile> settles;
    private int kingTax;
    private ArrayList<ResourceChangeListener> resListeners;
    private ArrayList<PopulationChangeListener> popListeners;
    private ArrayList<LandChangeListener> landListeners;
    private ArrayList<ScoreChangeListener> scoreListeners;
    private int tileScore;
    private int populationScore;
    private int resourceScore;
    private int improveScore;
    private int unitScore;
    private int totalScore;
    private int probation;
    private int last_prob;
    private int currentTurn;
    private boolean lost;

    public Player(GameData data, String name, Color c) {
        this.data = data;
        this.name = name;
        this.color = c;
        tiles = new ArrayList<Tile>();
        units = new ArrayList<Tile>();
        settles = new ArrayList<Tile>();
        resources = new ArrayList<ResourceAmount>();
        for (Resource r : data.getGame().getAllResources()) {
            resources.add(new ResourceAmount(r, 0));
        }
        kingTax = 0;

        tileScore = 0;
        populationScore = 0;
        resourceScore = 0;
        improveScore = 0;
        unitScore = 0;
        totalScore = 0;
        resListeners = new ArrayList<ResourceChangeListener>();
        popListeners = new ArrayList<PopulationChangeListener>();
        landListeners = new ArrayList<LandChangeListener>();
        scoreListeners = new ArrayList<ScoreChangeListener>();

        probation = 0;
        last_prob = 0;
        currentTurn = 0;
        lost = false;
    }

    public void addResourceListener(ResourceChangeListener rcl) {
        resListeners.add(rcl);
    }

    public void removeResourceListener(ResourceChangeListener rcl) {
        resListeners.remove(rcl);
    }

    public void fireResourceListeners(ResourceAmount ra) {
        for (ResourceChangeListener rcl : resListeners) {
            rcl.resourceChanged(this, ra);
        }
        calculateScore();
    }

    public void addPopulationListener(PopulationChangeListener pcl) {
        popListeners.add(pcl);
    }

    public void removePopulationListener(PopulationChangeListener pcl) {
        popListeners.remove(pcl);
    }

    public void firePopulationListeners(int total) {
        for (PopulationChangeListener pcl : popListeners) {
            pcl.populationChanged(this, total);
        }
        calculateScore();
    }

    public void addLandListener(LandChangeListener pcl) {
        landListeners.add(pcl);
    }

    public void removeLandListener(LandChangeListener pcl) {
        landListeners.remove(pcl);
    }

    public void fireLandListeners(Tile t) {
        for (LandChangeListener pcl : landListeners) {
            pcl.landChanged(this, t);
        }
        calculateScore();
    }

    public void addScoreListener(ScoreChangeListener pcl) {
        scoreListeners.add(pcl);
    }

    public void removeScoreListener(ScoreChangeListener pcl) {
        scoreListeners.remove(pcl);
    }

    public void fireScoreListeners(int score) {
        for (ScoreChangeListener pcl : scoreListeners) {
            pcl.scoreChanged(this, score);
        }
    }

    public void addUnit(Tile t) {
        if (!units.contains(t)) {
            units.add(t);
        }
    }

    public void removeUnit(Tile t) {

        units.remove(t);
    }

    public ArrayList<Tile> getTiles() {
        return tiles;
    }

    public void addSettle(Tile t) {
        if (!settles.contains(t)) {
        settles.add(t);
        }
    }

    public void removeSettle(Tile t) {
        settles.remove(t);
    }

    public ArrayList<Tile> getSettles() {
        return settles;
    }

    public int getTotalPopulation() {
        int total_pop = 0;
        for (Tile t : settles) {
            total_pop += t.getPopulation();
        }
        return total_pop;
    }

    public boolean beginTurn() {
        currentTurn++;
        grow();
        if (!lost) {
            collectTaxes();
        }
        if (!lost) {
            collectResources();
        }
        if (!lost) {
            payRoyalty();
        }
        if (!lost) {
            calculateScore();
        }
        if (this.lost) {
            destroy();
        }
        return this.lost;
    }

    public void grow() {
        for (Tile tile : tiles) {
            if (tile.getImprovement() != null && tile.getImprovement() instanceof Settlement) {
                Settlement s = (Settlement) tile.getImprovement();
                int growth = s.getPopulationGrowth() + (s.getPopulationFactor() * tile.getPopulation() / 100);
                int actual = tile.addPopulation(growth);
                if (actual != 0) {
                    data.getGui().addMessage(this, s.getName() + "'s population grows to " + tile.getPopulation() + " (+" + growth + ")");
                }
                if (tile.getPopulation() == s.getMaxPopulation()) {
                    data.getGui().addMessage(this, s.getName() + " is at max population.");
                }
            }
        }
    }

    public void collectTaxes() {
        int total_tax = 0;
        for (Tile tile : tiles) {
            if (tile.getImprovement() != null && tile.getImprovement() instanceof Settlement) {
                Settlement s = (Settlement) tile.getImprovement();
                int tax_factor = s.getTaxRate() * tile.getPopulation() / 100;
                int before = getResourceNum(data.getGame().getResource("Money"));
                int after = this.addResource(data.getGame().getResource("Money"), tax_factor);
                total_tax += after - before;
                //data.getGui().addMessage(this, s.getName() + " pays $" + tax_factor + " in taxes");
            }
        }
        if (total_tax != 0) {
            data.getGui().addMessage(this, "Receives $" + total_tax + " from population tax");
        }
    }

    public void collectResources() {
        HashMap<Resource, Integer> received = new HashMap<Resource, Integer>();
        HashMap<Player, Integer> taxed = new HashMap<Player, Integer>();
        for (Tile tile : units) {
            ArrayList<UnitStack> tu = tile.getUnits(this);
            for (UnitStack tun : tu) {
                if (tun.getType().hasSkill("collect")) {
                    //If not the owner
                    if (tile.getOwner() != null && tile.getOwner() != this) {
                        int tax_amt = payTax(tile.getOwner(), tun.getAmount());
                        if (taxed.containsKey(tile.getOwner())) {
                            tax_amt += taxed.get(tile.getOwner());
                        }
                        taxed.put(tile.getOwner(), tax_amt);
                    }
                    //Receive terrain bonus regardless of ownership
                    Terrain terrain = tile.getTerrain();
                    if (terrain != null) {
                        boolean bonus = false;
                        if (tile.getOwner() == this) {
                            ArrayList<PathInfo> path = data.getMap().findPath(tile, settles, Settlement.getMaxRange(), true, false, this);
                            if (path != null && !path.isEmpty()) {
                                if (path.get(0).getCost() <= data.getGame().getBaseSettlement().getLevel()) {
                                    bonus = true;
                                }
                            }
                        }
                        for (ResourceAmount res : terrain.getBonuses()) {
                            int amt = res.getAmount() * (bonus ? 2 : 1);// * tun.getAmount();
                            addResource(res.getResource(), amt);
                            if (!received.containsKey(res.getResource())) {
                                received.put(res.getResource(), amt);
                            } else {
                                int c_amt = received.get(res.getResource());
                                received.put(res.getResource(), amt + c_amt);
                            }
                        }

                        //Only receive improvement bonus if owner
                        Improvement imp = tile.getImprovement();
                        if (imp != null && tile.getOwner() == this) {
                            for (ResourceAmount res : imp.getBonuses()) {
                                int amt = res.getAmount() * ((bonus && !(imp instanceof Settlement)) ? 2 : 1);// * tun.getAmount();
                                addResource(res.getResource(), amt);
                                if (!received.containsKey(res.getResource())) {
                                    received.put(res.getResource(), amt);
                                } else {
                                    int c_amt = received.get(res.getResource());
                                    received.put(res.getResource(), amt + c_amt);
                                }
                            }
                        }
                    }
                }
            }
        }
        String resource_message = "";
        for (Entry<Resource, Integer> e : received.entrySet()) {
            if (!resource_message.isEmpty()) {
                resource_message += ", ";
            }
            if (e.getKey().equals(data.getGame().getResource("Money"))) {
                resource_message += "$" + e.getValue();
                //data.getGui().addMessage(this, "collected $" + e.getValue());
            } else {
                resource_message += e.getValue() + " " + e.getKey().getName();
                //data.getGui().addMessage(this, "collected " + e.getValue() + " " + e.getKey().getName());
            }
        }
        if (!resource_message.isEmpty()) {
            data.getGui().addMessage(this, "Collects " + resource_message);
        }
        for (Entry<Player, Integer> e : taxed.entrySet()) {
            data.getGui().addMessage(this, "Paid $" + e.getValue() + " in tax to " + e.getKey().getName());
        }

    }

    public void payRoyalty() {
        this.addResource(data.getGame().getResource("Money"), -kingTax);
        data.getGui().addMessage(this, "Pays $" + kingTax + " to the king.");
    }

    public int payTax(Player taxman, int multiplier) {
        taxman.addResource(data.getGame().getResource("Money"), 10 * multiplier);
        addResource(data.getGame().getResource("Money"), -10 * multiplier);
        return 10 * multiplier;
    }

    public void refreshUnits() {
        for (Tile t : units) {
            ArrayList<UnitStack> tu = t.getUnits(this);
            for (UnitStack tun : tu) {
                tun.freshenUp();
            }
        }
    }

    /**
     * Gets the number of units of the given resource the player currently possesses
     * @param r
     * @return 
     */
    public int getResourceNum(Resource r) {
        for (ResourceAmount ra : resources) {
            if (ra.getResource() == r) {
                return ra.getAmount();
            }
        }
        resources.add(new ResourceAmount(r, 0));
        return 0;
    }

    /**
     * Adds [num] units of the given resource [r] to the player's pool
     * @param r
     * @param num 
     */
    public int addResource(Resource r, int num) {
        //int n = getResourceNum(r) + num;
        for (ResourceAmount ra : resources) {
            if (ra.getResource() == r) {
                ra.addAmount(num);
                fireResourceListeners(ra);
                if (ra.getAmount() < 0 && ra.getResource() != data.getGame().getResource("Money")) {
                    ra.setAmount(0);
                }
                return ra.getAmount();
            }
        }
        return 0;
    }

    public void calculateScore() {

        tileScore = 0;
        improveScore = 0;
        resourceScore = 0;
        populationScore = 0;
        unitScore = 0;

        for (Tile t : tiles) {
            tileScore += t.getTerrain().getScore();
            if (t.getImprovement() != null) {
                improveScore += t.getImprovement().getScore();
            }
            if (t.getPopulation() > 0) {
                populationScore += t.getPopulation();
            }
        }
        for (Tile t : units) {
            ArrayList<UnitStack> stack = t.getAllUnits(this);
            for (UnitStack u : stack) {
                unitScore += u.getType().getScore() * u.getAmount();
            }
        }

        for (ResourceAmount ra : resources) {
            resourceScore += ra.getResource().getScore() * ra.getAmount();
        }
        totalScore = tileScore + improveScore + resourceScore + populationScore + unitScore;

        data.getGui().refreshContent();
        fireScoreListeners(totalScore);
    }

    public int getProbation() {
        return probation;
    }

    public void setProbation(int probation) {
        this.probation = probation;
    }

    public ArrayList<ResourceAmount> getResources() {
        return resources;
    }

    public int getImproveScore() {
        return improveScore;
    }

    public int getKingTax() {
        return kingTax;
    }

    public int getPopulationScore() {
        return populationScore;
    }

    public int getResourceScore() {
        return resourceScore;
    }

    public int getTileScore() {
        return tileScore;
    }

    public int getTotalScore() {
        return totalScore;
    }

    public int getUnitScore() {
        return unitScore;
    }

    public Color getColor() {
        return color;
    }

    public String getName() {
        return name;
    }

    public void addTile(Tile t) {

        if (!tiles.contains(t)) {
            tiles.add(t);
            fireLandListeners(t);
        }
    }

    public void removeTile(Tile t) {
        if (tiles.remove(t)) {
            fireLandListeners(t);
        }
    }

    public int getImprovementNum(Improvement i) {
        int n = 0;
        for (Tile t : tiles) {
            if (t.getImprovement() == i) {
                n++;
            }
        }
        return n;
    }

    public int getCurrentTurn() {
        return currentTurn;
    }

    public void setCurrentTurn(int currentTurn) {
        this.currentTurn = currentTurn;
    }

    public int getLastProb() {
        return last_prob;
    }

    public void setLastProb(int last_prob) {
        this.last_prob = last_prob;
    }

//    public int getMbPosition() {
//        return mbPosition;
//    }
//
////    public void setMbPosition(int mbPosition) {
////        this.mbPosition = mbPosition;
////    }
//    public void moveMbPosition(int pos) {
//        this.mbPosition += pos;
//        int sz = data.getMiniboard().size();
//        if (mbPosition < 0) {
//            mbPosition += sz;
//        } else if (mbPosition >= sz) {
//            mbPosition -= sz;
//        }
//        
//    }
    @Override
    public void revolution(Player p) {
        if (kingTax < 20) {
            kingTax += 4;
        } else if (kingTax < 100) {
            kingTax += 8;
        } else {
            kingTax += (kingTax / 10);
        }
        data.getGui().addMessage(p, "Royalty tax has increased to $" + kingTax);
    }

    public void lose() {
        this.lost = true;
    }

    public void destroy() {
        ArrayList<Tile> tcopy = new ArrayList<Tile>();
        tcopy.addAll(tiles);
        for (Tile t : tcopy) {
            if (t.getImprovement() != null) {
                t.setImprovement(null);
            }
            t.setOwner(null);
        }
        tcopy.clear();
        tcopy.addAll(units);
        for (Tile t : tcopy) {
            for (UnitStack stack : t.getAllUnits(this)) {
                t.removeUnit(this, stack.getType(), stack.getAmount());
            }
        }
        data.getMiniboard().removeMarker(this);
        data.getRoster().losePlayer(this);
        if (data.getRoster().getPlayers().size() > 1) {
            Client.get().sendMessage(Messages.LOSER, new String[]{name});
        } else {
            Client.get().sendMessage(Messages.WINNER, new String[]{data.getRoster().getPlayers().get(0).getName()});
        }
    }
}
